﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SuperSocket.Common;
using SuperSocket.SocketBase.Protocol;

namespace SuperSocket.SocketBase
{
    public abstract class AppServer : AppServer<AppSession>
    {
        protected AppServer()
        {

        }

        protected AppServer(IRequestFilterFactory<StringRequestInfo> requestFilterFactory)
            : base(requestFilterFactory)
        {

        }
    }

    public abstract class AppServer<TAppSession> : AppServer<TAppSession, StringRequestInfo>
        where TAppSession : IAppSession, IAppSession<TAppSession, StringRequestInfo>, new()
    {
        protected AppServer()
            : base(new CommandLineRequestFilterFactory())
        {

        }

        protected AppServer(IRequestFilterFactory<StringRequestInfo> requestFilterFactory)
            : base(requestFilterFactory)
        {

        }
    }

    public abstract class AppServer<TAppSession, TRequestInfo> : AppServerBase<TAppSession, TRequestInfo>, IPerformanceDataSource
        where TRequestInfo : IRequestInfo
        where TAppSession : IAppSession<TAppSession, TRequestInfo>, new()
    {
        protected AppServer()
        {
            
        }

        protected AppServer(IRequestFilterFactory<TRequestInfo> protocol)
            : base(protocol)
        {

        }

        /// <summary>
        /// Starts this AppServer instance.
        /// </summary>
        /// <returns></returns>
        public override bool Start()
        {
            if (!base.Start())
                return false;

            if (!Config.DisableSessionSnapshot)
                StartSessionSnapshotTimer();

            if (Config.ClearIdleSession)
                StartClearSessionTimer();

            return true;
        }

        private ConcurrentDictionary<string, TAppSession> _mSessionDict = new ConcurrentDictionary<string, TAppSession>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// Creates the app session base one socketSession.
        /// </summary>
        /// <param name="socketSession">The socket session.</param>
        /// <returns></returns>
        public override IAppSession CreateAppSession(ISocketSession socketSession)
        {
            var baseAppSession = base.CreateAppSession(socketSession);

            if (baseAppSession == null)
                return null;

            var appSession = (TAppSession)baseAppSession;

            if (_mSessionDict.TryAdd(appSession.SessionID, appSession))
            {
                if(!Logger.IsInfoEnabled)
                    Logger.Info(appSession, "New SocketSession was accepted!");
                return appSession;
            }
            if(Logger.IsErrorEnabled)
                Logger.Error(appSession, "SocketSession was refused because the session's IdentityKey already exists!");
            return NullAppSession;
        }

        protected override IAppSession GetAppSessionByIdInternal(string sessionId)
        {
            return GetAppSessionById(sessionId);
        }

        /// <summary>
        /// Gets the app session by ID.
        /// </summary>
        /// <param name="sessionId">The session ID.</param>
        /// <returns></returns>
        public TAppSession GetAppSessionById(string sessionId)
        {
            if (string.IsNullOrEmpty(sessionId))
                return NullAppSession;

            TAppSession targetSession;
            _mSessionDict.TryGetValue(sessionId, out targetSession);
            return targetSession;
        }

        /// <summary>
        /// Called when [socket session closed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="SuperSocket.SocketBase.SocketSessionClosedEventArgs"/> instance containing the event data.</param>
        internal protected override void OnSocketSessionClosed(object sender, SocketSessionClosedEventArgs e)
        {
            string sessionId = e.SessionID;

            if (string.IsNullOrEmpty(sessionId))
                return;

            TAppSession removedSession;
            if (_mSessionDict.TryRemove(sessionId, out removedSession))
            {
                removedSession.Status = SessionStatus.Disconnected;
                if(Logger.IsInfoEnabled)
                    Logger.Info(removedSession, "This session was closed!");

                Async.Run(() => OnAppSessionClosed(this, new AppSessionClosedEventArgs<TAppSession>(removedSession, e.Reason)),
                    exc => Logger.Error(exc));
            }
            else
            {
                if (Logger.IsErrorEnabled)
                    Logger.Error(removedSession, "Failed to remove this session, Because it haven't been in session container!");
            }
        }

        /// <summary>
        /// Called when [app session closed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="SuperSocket.SocketBase.AppSessionClosedEventArgs&lt;TAppSession&gt;"/> instance containing the event data.</param>
        protected virtual void OnAppSessionClosed(object sender, AppSessionClosedEventArgs<TAppSession> e)
        {

        }

        /// <summary>
        /// Gets the total session count.
        /// </summary>
        public override int SessionCount
        {
            get
            {
                return _mSessionDict.Count;
            }
        }

        #region Clear idle sessions

        private Timer _mClearIdleSessionTimer;

        private void StartClearSessionTimer()
        {
            int interval = Config.ClearIdleSessionInterval * 1000;//in milliseconds
            _mClearIdleSessionTimer = new Timer(ClearIdleSession, new object(), interval, interval);
        }

        /// <summary>
        /// Clears the idle session.
        /// </summary>
        /// <param name="state">The state.</param>
        private void ClearIdleSession(object state)
        {
            if (Monitor.TryEnter(state))
            {
                try
                {
                    DateTime now = DateTime.Now;
                    DateTime timeOut = now.AddSeconds(0 - Config.IdleSessionTimeOut);

                    var timeOutSessions = SessionSource.Where(s => s.Value.LastActiveTime <= timeOut).Select(s => s.Value);
                    Parallel.ForEach(timeOutSessions, s =>
                        {
                            if (Logger.IsInfoEnabled)
                                Logger.Info(s, string.Format("The socket session has been closed for {0} timeout, last active time: {1}!", now.Subtract(s.LastActiveTime).TotalSeconds, s.LastActiveTime));
                            s.Close(CloseReason.TimeOut);
                        });
                }
                catch (Exception e)
                {
                    if(Logger.IsErrorEnabled)
                        Logger.Error("Clear idle session error!", e);
                }
                finally
                {
                    Monitor.Exit(state);
                }
            }
        }

        private IEnumerable<KeyValuePair<string, TAppSession>> SessionSource
        {
            get
            {
                return Config.DisableSessionSnapshot 
                    ? _mSessionDict.ToArray() 
                    : _mSessionsSnapshot;
            }
        }

        #endregion

        #region Take session snapshot

        private Timer _mSessionSnapshotTimer;

        private KeyValuePair<string, TAppSession>[] _mSessionsSnapshot = new KeyValuePair<string, TAppSession>[0];

        private void StartSessionSnapshotTimer()
        {
            int interval = Math.Max(Config.SessionSnapshotInterval, 1) * 1000;//in milliseconds
            _mSessionSnapshotTimer = new Timer(TakeSessionSnapshot, new object(), interval, interval);
        }

        private void TakeSessionSnapshot(object state)
        {
            if (Monitor.TryEnter(state))
            {
                Interlocked.Exchange(ref _mSessionsSnapshot, _mSessionDict.ToArray());
                Monitor.Exit(state);
            }
        }

        #endregion

        #region Search session utils

        /// <summary>
        /// Gets the matched sessions from sessions snapshot.
        /// </summary>
        /// <param name="critera">The prediction critera.</param>
        /// <returns></returns>
        public override IEnumerable<TAppSession> GetSessions(Func<TAppSession, bool> critera)
        {
            return SessionSource.Select(p => p.Value).Where(critera);
        }

        /// <summary>
        /// Gets all sessions in sessions snapshot.
        /// </summary>
        /// <returns></returns>
        public override IEnumerable<TAppSession> GetAllSessions()
        {
            return SessionSource.Select(p => p.Value);
        }

        #endregion

        #region Performance logging

        private PerformanceData _mPerformanceData = new PerformanceData();

        public PerformanceData CollectPerformanceData(GlobalPerformanceData globalPerfData)
        {
            _mPerformanceData.PushRecord(new PerformanceRecord
                {
                    TotalConnections = _mSessionDict.Count,
                    TotalHandledRequests = TotalHandledRequests
                });

            //User can process the performance data by self
            Async.Run(() => OnPerformanceDataCollected(globalPerfData, _mPerformanceData), e => Logger.Error(e));

            return _mPerformanceData;
        }

        //User can override this method a get collected performance data
        protected virtual void OnPerformanceDataCollected(GlobalPerformanceData globalPerfData, PerformanceData performanceData)
        {

        }

        #endregion

        #region IDisposable Members

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {                
                if (_mSessionSnapshotTimer != null)
                {
                    _mSessionSnapshotTimer.Change(Timeout.Infinite, Timeout.Infinite);
                    _mSessionSnapshotTimer.Dispose();
                    _mSessionSnapshotTimer = null;
                }

                if (_mClearIdleSessionTimer != null)
                {
                    _mClearIdleSessionTimer.Change(Timeout.Infinite, Timeout.Infinite);
                    _mClearIdleSessionTimer.Dispose();
                    _mClearIdleSessionTimer = null;
                }

                var sessions = _mSessionDict.ToArray();

                if(sessions.Length > 0)
                {
                    var tasks = new Task[sessions.Length];

                    for (var i = 0; i < tasks.Length; i++)
                    {
                        tasks[i] = Task.Factory.StartNew(
                            s => ((TAppSession)s).Close(CloseReason.ServerShutdown), sessions[i].Value);
                    }

                    Task.WaitAll(tasks);
                }
            }
        }

        #endregion
    }
}
